package com.zhenhunfan.io.model.nio.demo01.demo01;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * <p>Title: NioServer</p>
 * <p>Description: 基于nio的reactor模型</p>
 *
 * @author zhenhunfan
 * @version 1.0.0
 * @date 2021/4/1
 */
public class NioServer {
    //多路复用器，主要作用是轮询TCP缓冲区中每个网络IO的数据是否准备好了
    private final Selector selector;
    //NIO对ServerSocket的封装，这个通道是双向的，可读可写
    private final ServerSocketChannel serverSocketChannel;

    public NioServer(int port) throws IOException { //reactor 初始化
        serverSocketChannel = ServerSocketChannel.open();//建立一个Server端通道
        serverSocketChannel.socket().bind(new InetSocketAddress(port));//绑定服务端口
        serverSocketChannel.configureBlocking(false);//设定为非阻塞的模型，selector模式下，所有通道必须是非阻塞的

        System.out.println("Server started，listening on: " + serverSocketChannel.getLocalAddress());

        selector = Selector.open();//打开一个Selector，找内核申请多路复用器
        //服务器通道告诉selector去关注accept事件（可以理解成客户端（比方说浏览器）发送连接请求了），一旦有accept事件
        //则生成一个SelectionKey sk去处理，怎么处理呢？就是给sk绑定事件处理器
        //最初给一个channel注册上去的事件都是accept
        SelectionKey sk = serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
        //attach callback object，Acceptor
        sk.attach(new Acceptor(serverSocketChannel,selector));//绑定接收事件处理器
    }

    public void run(){
        try {
            while (true){ //Selector 轮询一次

                selector.select();//感兴趣的事件到达之前，阻塞

                //keys可以想象成是ServerSockerChannel上的连接位，客户端连接服务器就要占一个连接位
                //那么selector.select()就是挑选出感兴趣的事件发生的连接位
                Set<SelectionKey> keys = selector.selectedKeys();//拿到本次select获取的就绪事件
                Iterator it = keys.iterator();
                while (it.hasNext()){
                    //处理连接位
                    dispatch((SelectionKey)(it.next()));
                }
                //一定要清理掉，否则会重复处理事件
                keys.clear();
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            if(selector != null){
                try {
                    selector.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    void dispatch(SelectionKey k){
        Runnable r = (Runnable)(k.attachment());
        //调用之前注册的callback对象
        if(r != null){
            //只是拿到句柄执行run方法，并没有起新线程
            //新起线程的写法为new Thread(r).start();
            r.run();
        }
    }

    public static void main(String[] args) throws IOException {
        NioServer server = new NioServer(8080);
        server.run();
    }
}
